Python Django
合并自:Web框架导论.md、Django快速上手.md、Django项目规范.md、Django项目部署.md 更新:2026-05-22
Web框架基础
1 web框架底层
1.1 网络通信
image-20220619092142226-20250830192832-76gnflg.pngimage-20220619092432693-20250830192832-4o9sy70.png服务端
pythonimport socket # 1.监听本机的IP和端口 sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) sock.bind(("127.0.0.1", 8899)) # 2.最大连接数 sock.listen(5) while True: # 3.等待客户端连接,阻塞函数 conn, addr = sock.accept() # 4.连接成功后立即发送 conn.sendall("欢迎使用xx系统".encode("utf-8")) # 5.断开连接 conn.close() # 6.停止服务端程序 sock.close()客户端
pythonimport socket # 1. 向指定IP发送连接请求 client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect(("127.0.0.1", 8899)) # 2. 接收服务端的消息 message = client.recv(1024) print(message.decode("utf-8")) # 3.断开连接 client.close()
1.2 常见软件架构
bs架构:服务器-浏览器
cs架构:服务端-客户端
开发应用程序,例如:QQ、Pycharm、网易云音乐(安装在电脑上的软件)
开发区别:
网站,只需要写服务端程序
基于django开发的本质就是网站(web应用) 电脑上浏览器本质上是socket实现网络通信软件,客户端 + 服务端
1.3 手撸web框架
import socket
# 1.监听本机的IP和端口
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
sock.bind(('192.168.0.6', 9000)) # 我自己的电脑IP,端口8001
# 2.让多少人等待
sock.listen(5)
while True:
# 3.等待连接请求的申请,有人来连接(阻塞) -> 登录浏览器来连接我
conn, addr = sock.accept()
# 4.收到浏览器发送的消息
buf = conn.recv(2048)
print(buf)
# 5.给浏览器返回数据
conn.send(b"HTTP/1.1 200 OK\r\n\r\n")
conn.send(b"Hello, World")
# 6.断开连接
conn.close()
# 6.停止服务端程序
sock.close()浏览器与服务端的网站进行通信
服务端:网站
客户端:浏览器
创建连接
发送数据,固定格式
GET 请求
# 请求头首行 GET /xxx/xxx/?name=xxx&age=111 HTTP/1.1\r\n # 请求头 Host: 192.168.0.6:9000\r\n Connection: keep-alive\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n Accept-Encoding: gzip, deflate\r\n Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7\r\n\r\n'POST 请求
# 请求头首行 POST /xxx/xxx/ HTTP/1.1\r\n # 请求头 Host: 192.168.0.6:9000\r\n Connection: keep-alive\r\n Upgrade-Insecure-Requests: 1\r\n User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.0.0 Safari/537.36\r\n Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9\r\n Accept-Encoding: gzip, deflate\r\n Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7\r\n\r\n # 请求体 username=wupeiqi&password=123浏览器本质上发送请求时,包含请求头和请求体
- GET请求 - 只有请求头 + 没有请求体 - 请求头之间用 \r\n - POST请求 - 只有请求头 + 有请求体 - 请求头之间用 \r\n - 请求头和请求体之间用 \r\n\r\n一次请求和一次响应后,断开连接
两个关键点:
http协议
特点:无状态的短连接
http的应用:浏览器向服务端发送请求,需用通过http协议来实现
python- 请求头+请求体 ;请求头和请求体之间用 \r\n\r\n ;请求头之间用 \r\n - 一次请求和一次响应后,断开连接。 -> 短连接 ->无状态如何体现? - 后期: 请求头+cookieGET请求和POST请求的区别
GET 用于无副作用地获取资源,参数放 URL、有限且暴露;POST 用于提交数据,参数放 body、长度大且不暴露,但需防止重复提交。
2 web框架
常见的web框架:django、flask、tornado、sanic、fastapi等等
image-20220619111206007-20250830192832-dmdqma1.pngweb应用程序:
- socket
- web框架
- 业务开发
以django为例:
wsgiref模块、uWSGI、daphne->本质上都是socket实现。- 原来实现了框架
以flask为例:
werkzurg、uwsgi、...flask框架
以tornado为例:
tornado、werkzurg、uwsgi、...- 框架
2.1 wsgiref
from wsgiref.simple_server import make_server
def run_server(environ, start_response):
start_response('200 OK', [('Content-Type', 'text/html')])
return [bytes('<h1>Hello, web!</h1>', encoding='utf-8'), ]
if __name__ == '__main__':
httpd = make_server('127.0.0.1', 8000, run_server)
httpd.serve_forever()2.2 werkzeug
pip install werkzeugfrom werkzeug.wrappers import Response
def application(environ, start_response):
response = Response('Hello World!', mimetype='text/plain')
return response(environ, start_response)
if __name__ == '__main__':
from werkzeug.serving import run_simple
run_simple('localhost', 4000, application)2.3 各框架的区别
django、flask、tornado、sanic、fastapi..以内部集成功能的多少进行划分
- django,内部提供了很多组件 【相对大】
- flask、tornado、sanic、fastapi... 本身功能很少 + 第三方组件【相对小】
同步框架 vs 异步非阻塞
异步非阻塞:tornado、sanic、fastapi、django
同步:django、flask、bottle、webpy
1.django、flask 2.tornado,异步非阻塞,特别NB。 - 同步:常见应用。 - 异步:IO应用 + conroutine装饰器 + redis/MySQL/... 3.sanic,路飞小猿圈平台 4.fastapi - 参考flask - py最新注解 - restfulAPI - 异步 目前不看好: - 增加编程的难度,功能&效率 - 项目中不会有那么IO操作 ---> 100功能/2-IO ---> celery
异步非阻塞框架是如何实现的?
- socket & 多线程 & 多进程
- IO多路复用
参考:https://www.cnblogs.com/wupeiqi/p/6536518.html
⚠️ 图片缺失,需补回:image-20220619120944641-20250830192832-0xbm34u.png⚠️ 图片缺失,需补回:image-20220619122238391-20250830192832-evuhsjc.png
小结
django项目开发,在局域网部署访问。所有人访问云服务器。
bs架构、cs架构。
基于socket实现web框架(所有web框架的祖宗)
常见web框架的区别。
- 功能
- 异步非阻塞
Django 快速上手
1 基础
1.1 快速上手django框架
1)安装
pip install django==3.2C:\Python39
- python.exe
- Scripts
- pip.exe
- django-admin.exe
- Lib
- re.py
- random.py
- site-pakages
- django==3.2
...2)命令行创建django项目
创建项目
cd 指定目录 django-admin startproject 项目名⚠️ 图片缺失,需补回:image-20220619144658273-20250830192832-nwldykf.png⚠️ 图片缺失,需补回:image-20220619144642767-20250830192832-ueyqaom.png创建项目自动生成的文件
mysite ├── manage.py [项目的管理工具] └── mysite ├── __init__.py ├── settings.py 【配置文件,只有一部分。程序启动时,先读取django内部配置,再读settings.py】 ├── urls.py 【主路由,在里面编写 /xxx/xxx/xxx ---> index】 ├── asgi.py 【异步】 └── wsgi.py 【同步,主】编写
urls.pypythonfrom django.contrib import admin from django.urls import path from django.shortcuts import HttpResponse def info(request): print("请求来执行了") return HttpResponse("xxxx") def xxxx(request): print("请求来执行了") return HttpResponse("。。。。。。") urlpatterns = [ # path('admin/', admin.site.urls), path('api/index/', info), path('api/show/', xxxx), ]运行
pythoncd 项目 python manage.py runserver python3.9 manage.py runserver python3.9 manage.py runserver 127.0.0.1:8000 python3.9 manage.py runserver 127.0.0.1:9000app概念
pythoncd 项目 python manage.py startapp 名称mysite ├── manage.py [项目的管理工具] ├── web ├── __init__.py ├── views.py [视图函数] ├── models.py [ORM,基于models可以对数据库进行简便的操作] ... └── mysite ├── __init__.py ├── settings.py 【配置文件,只有一部分。程序启动时,先读取django内部配置,再读settings.py】 ├── urls.py 【主路由,在里面编写 /xxx/xxx/xxx ---> index 】 ├── asgi.py 【异步】 └── wsgi.py 【同步,主】示例:
pythonpython manage.py startapp web 将自动生成web文件mysite ├── manage.py ├── mysite │ ├── __init__.py │ ├── asgi.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py └── web ├── __init__.py ├── admin.py 【管理】 ├── apps.py 【基础配置】 ├── migrations 【其中内容自动生成不需要动】 │ └── __init__.py ├── models.py ├── tests.py 【单元测试】 └── views.py【Tip】主要对
models.py和views.py进行编写urls.pypythonfrom django.contrib import admin from django.urls import path from web import views urlpatterns = [ # path('admin/', admin.site.urls), path('api/index/', info), path('api/show/', xxxx), ]
3)虚拟环境 - 命令行
venv,Python官方用于创建虚拟环境的工具(python3开始支持)
python3.9 -m venv name python3.9 -m venv pathvirtualenv 【推荐】
pip install virtualenvcd /xxx/xx/ virtualenv name --python=python3.9 virtualenv path --python=python3.7激活虚拟环境
- win:
activate - mac:
source /虚拟环境目录/bin/activate
- win:
退出虚拟环境:
deactivate
创建django项目
django-admin startproject xxxx
python manage.py startapp xxxx
python manage.py runserver4)Pycharm
专业版
新建项目 - Django - 项目路径(名称)- 选择已经安装Django 的Python
社区版(仅支持使用虚拟环境)
新建普通Python项目
安装Django
pythonpip install django pip install django==3.2 # 指定版本打开终端
pythondjango-admin startproject 项目名
此时在当前项目下,将创建好一个django项目
运行项目
pythonpython manage.py runserver创建app
pythonpython manage.py startapp app名称项目示例:
创建app:
python manage.py startapp weburl.pypythonfrom django.contrib import admin from django.urls import path from web import views urlpatterns = [ # path('admin/', admin.site.urls), path('admin/', admin.site.urls), path('xxxx/', views.site.urls), ]views.pypythonfrom django.shortcuts import render from django.shortcuts import HttpResponse def xxxx(request): return HttpResponse("stars")运行项目:
python manage.py runserver
配置
专业版:
add configuration→Add New Configuration(左上角 + )→Django Servername:配置名称 → 点击右下角
Fix进入
Preferences- 勾选 Enable Django Support
- Django project root:项目根目录
- Settings:项目中的
settings.py - Manage script:项目中的
manage.py
社区版:
点击运行按钮右边的 按钮(三个点)→ 编辑
Add New Configuration(左上角 + )→ Python- name:配置名称
- 选择运行环境
- Script path:
manage.py - Parameters(脚本形参): 输入
runserver - Working directory(工作目录):项目根目录
1.2 关于创建 App
项目只需要一个app,目录结构的建议
pythonproject .venv project ... ... manage.py app1项目有多个app
pythonproject .venv project ... ... manage.py apps web backend api创建方式:
python# 在 project 下创建 apps/api 文件夹 python manage.py startapp api apps/api # 创建app(终端) # 打开 api 中自动生成的apps.py name = "apps.api"注册安装app
python# settings INSTALLED_APPS = [ 'apps.appname.apps.AppnameConfig' ] 整体路径为app中的apps.py的类名称
1.3 纯净版 Django
先建一个纯 Python 工程,再手动
pip install django==x.x,然后用django-admin startproject xxx .创建项目骨架。接着把 settings.py 中“暂时用不到”的 APP、中间件、模板 context_processors 统统注释或删除,只留下
django.contrib.staticfiles(有时也删)和你真正需要的第三方 APP(如 rest_framework)。典型裁剪结果:
INSTALLED_APPS 只剩 2~3 项;MIDDLEWARE 去掉 Session、Message、Authentication 相关;TEMPLATES 去掉 auth、messages 的 processor;数据库默认配置可先留空。需要时再自行安装/配置:MySQL、Redis、JWT、CORS 等。
目标是把 Django 退化成“一个轻量 WSGI 框架”,更适合:
- 纯
RESTful API服务 - 微服务中的一个独立服务
- Serverless 或容器镜像要求最小化的场景
- 纯
2 路由系统
本质上:URL和函数的对应关系。
2.1 传统的路由
urls.pypythonfrom django.contrib import admin from django.urls import path from apps.web import views urlpatterns = [ # path('admin/', admin.site.urls), path('home/', views.home), path('news/<int:nid>/edit/', views.news), path('login/', views.login), ]web\views.pypythonfrom django.shortcuts import render,HttpResponse def home(request): return HttpResponse("HOME") def news(request, nid): print(nid) page = request.GET.get("page") return HttpResponse("News") def login(request): nid = request.GET.get("nid") print(nid) return HttpResponse("login")【Tip】
int,整数str,字符串 排除/slug,字母数字下滑线-uuid,uuid格式path,路径,可以包含/
2.2 正则表达式 路由
在django1 版本用的多,在django2+ 版本用的少
urls.pypythonfrom django.contrib import admin from django.urls import path,re_path from apps.web import views urlpatterns = [ # path('admin/', admin.site.urls), # re_path(r'users/(\d+)/', views.users), # re_path(r'users/(\w+-\d+)/(\d+)/', views.users), re_path(r'users/(?P<xxid>\w+-\d+)/(?P<yid>\d+)/', views.users), ]要在
django.urls中导入:re_path?P<parameter name>:传递对应的参数web\views.pypythonfrom django.shortcuts import render,HttpResponse def users(request, xxid, yid): print(xxid, yid) return HttpResponse("Users")
2.3 路由分发
1)inlucde
inlucde + app 将功能拆分不到不同的app中
urls.pypythonfrom django.contrib import admin from django.urls import path,include from apps.web import views urlpatterns = [ path('api/', include("apps.api.urls")), path('web/', include("apps.web.urls")), ]要在
django.urls中导入:includeapi\urls.pypythonfrom django.urls import path from apps.api import views urlpatterns = [ # api/auth/ path('auth/', views.auth), # api/login/ path('login/', views.login), ]api\views.pypythonfrom django.shortcuts import HttpResponse def auth(request): return HttpResponse("Auth") def login(request): return HttpResponse("Login")web\urls.pypythonfrom django.urls import path,re_path from apps.web import views urlpatterns = [ # web/home/ path('home/', views.home), # web/article/ path('article/', views.article), # web/users/1xxx re_path(r'users/(\w+)/', views.users), ]web\views.pypythonfrom django.shortcuts import HttpResponse # Create your views here. def home(request): return HttpResponse("HOME") def article(request): nid = request.GET.get("nid") print(nid) return HttpResponse("Article") def users(request, xxid, yid): print(xxid, yid) return HttpResponse("Users")
2)手动分发
手动路由分发,可以与 app 无关
urls.pypythonfrom django.urls import path from apps.api import views urlpatterns = [ # path('user/add/', views.login), # path('user/delete/', views.login), # path('user/edit/', views.login), # path('user/list/', views.login), path('user/', ([ path('add/', views.login), path('delete/', views.login), path('edit/', views.login), path('list/', views.login), ], None, None)), ]这种写法能够,将功能提取,有助于开发的管理
3)路由分发的本质
include 和 手动分发(手动元组列表),本质相同
URL对应函数
pythonpath('user/add/', views.login)URL对应元组
path('user/add/', (元素,appname元素,namespance元素)),pythonpath('user/add/', include("apps.api.urls")), # include 返回元组 path('user/add/', ([],None,None)), # 这个元组等同于include的return元组三元组
pythonpath('user/', ([ path('add/', views.login), path('delete/', views.login), # /user/delete/ path('edit/', views.login), path('list/', views.login), ], None, None)),标准
include写法(内联方式)pythonpath('user/', include(([ path('add/', views.login), path('delete/', views.login), # /user/delete/ path('edit/', views.login), path('list/', views.login), ], None))),等价于
path('user/', include('add.urls')) # 更常见的写法特殊写法
pythonproject └──project └──urls.py └──apps └──api └──urls └──v1.py └──v2.py └──views.pyurls.pypythonfrom django.url import path, include urlpatterns = [ path('api/' include("apps.api.urls.v1")) path('api/' include("apps.api.urls.v2")) ]views.py也同样可以如上,在api中创建view目录 存放多个文件
2.4 name
给一个路由起个名字 + 根据名字反向生成URL
urlpatterns = [
path('login/', views.login, name="v1"),
path('auth/', views.auth, name="v2"),
]在视图函数中生成URL
Pythonfrom django.urls import reverse url = reverse("v2") # /auth/ url = reverse("v1") # /login/HTML模板,页面上有一个a标签,添加xx。
html<a href="/xxx/xxx/xx/">添加</a>html<a href="{% url 'v1' %}">添加</a> <a href="{% url 'v2' %}">添加</a>扩展
以后做权限管理,让name属性配合基于 name和正则 的反向生成(反向解析)
python# urls.py from django.urls import path,re_path from apps.api import views urlpatterns = [ re_path(r'xxx/(?P<nid>\d+)/(?P<tpl>\w+)/', views.auth, name='v3'), re_path(r'auth/(\d+)/(\w+)/', views.auth, name='v2'), path('login/<str:role>/', views.login, name='v1'), ]- 视图函数中
python# apps/api/views.py from django.shortcuts import HttpResponse from django.urls import reverse # name def auth(request, nid, tpl): return HttpResponse("auth") def login(request, role): # /login/hhh url = reverse("v1", kwargs={"role": "hhh"}) # 反向生成 URL print(url) # /auth/666/ppp url = reverse("v2", arge=(666,"ppp")) # 没有参数名,用 args print(url) # /xxx/666/ddd url = reverse("v3", kwargs={'nid':666, 'tpl':"ddd"}) # 反向生成 URL print(url) return HttpResponse("login")- html
pythonfrom django.shortcuts import HttpResponse,render # name def auth(request, nid, tpl): return HttpResponse("auth") def login(request, role): return render(request, 'login,html')html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>登录页面</h1> <a href="/xxx/xxxx/">调换</a> <a href="{% url 'v1' role='x1213123x' %}">跳转</a> <a href="{% url 'v2' 666 'x1213123x' %}">跳转</a> <a href="{% url 'v3' nid=666 tpl='x1213123x' %}">跳转</a> </body> </html>
2.5 namespace(命名空间)
作用:给一组 URL 取一个姓,也可以让 reverse() / 模板 {% url %} 永远能唯一定位到一条路由
主路由中加入
namespace,防止不同app中同名name撞车
主路由
urls.pypythonfrom django.urls import path, include urlpatterns = [ path('api/', include("apps.api.urls", namespace='x1')), path('web/', include("apps.web.urls", namespace='x2')), ]api/urls.pypythonfrom django.urls import path from . import views urlpatterns = [ path('login/', views.login, name="login"), path('auth/', views.auth, name='auth'), ]web/urls.pypythonfrom django.urls import path from . import views urlpatterns = [ path('home/', views.home, name='home'), path('order/', views.order, name='order'), path('auth/', views.order, name='auth'), ]在某个URL或者视图中反向生成:
pythonfrom django.urls import reverse url = reverse("x1:auth") # /api/login/ url = reverse("x2:auth") # /web/login/app_name应用名加入
namespace还需要设置app_namePython# api/urls/py from django.urls import path from apps.api import views urlpatterns = [ path('login/', views.login, name="login"), path('auth/', views.auth, name='auth'), ] app_name = "api" # 应用名 # urls.py from django.urls import path, include urlpatterns = [ path('api/', include("apps.api.urls", namespace='x1')), ] # api/views.py from django.http import HttpResponse from django.shortcuts import render from django.urls import reverse def login(request): url = reverse("x1: login") # 反向生成 URL print(url) return render(request, 'api/login.html') def auth(request): return HttpResponse("auth")
【扩展】
手动分发
Python# urls.py from django.urls import path from apps.api import views urlpatterns = [ path('api/', ([ path('auth/', views.auth, name='auth'), path('login/', views.login, name='login'), ], 'x1', 'x1')), # path(url, [urls], app_name, namespace) ] # api/views.py from django.http import HttpResponse from django.urls import reverse def login(request): url = reverse("x1:login") # 反向生成 URL print(url) return HttpResponse("login") def auth(request): return HttpResponse("auth")多层嵌套
Pythonfrom django.urls import path from apps.api import views urlpatterns = [ path('api/', ([ path('auth/', views.auth, name='auth'), path('login/', views.login, name='login'), path('x/', ([ path('xx/', views.login, name='xx'), # /api/x/xx/ x1:yy:xx path('xxx/', views.login, name='xxx'), # /api/x/xxx/ ], 'yy', 'yy')), ], 'x1', 'x1')), # path(url, [urls], app_name, namespace) ]
2.6 URL 最后的 /(slash)
APPEND_SLASH = True
path('login/', views.login),
http://127.0.0.1:8000/login/ 成功
http://127.0.0.1:8000/login django,重定向301(自动加 / )APPEND_SLASH = False 关闭后,则不会自动加 /
path('login/', views.login),
http://127.0.0.1:8000/login/ 成功
http://127.0.0.1:8000/login 失败若不加 /
path('login', views.login),
http://127.0.0.1:8000/login 成功
http://127.0.0.1:8000/login/ 失败2.7 当前匹配对象
# url.py
from django.urls import path
from apps.api import views
urlpatterns = [
path('login/', views.login, name='xx')
]
# apps/api/views.py
from django.http import HttpResponse
def login(request):
print(request.resolver_match)
# ResolverMatch(func=apps.api.views.login, args=(), kwargs={}, url_name=xx, app_names=[], namespaces=[], route=login/)
return HttpResponse("login")
def auth(request):
return HttpResponse("auth")作用:
- 某用户,具有一些权限。
permissions = ["xx","login",'account'] - 某用户,具有一些权限。
permissions = ["login",'account']
# apps/api/views.py
from django.http import HttpResponse
def login(request):
# 1. 当前用户具备的所有权限
permissins = ["xx", "login", "account"]
# 2. 判断是否具有权限
current_name = request.resolver_match.url_name # 获取当前name值
if current_name not in permissins:
return HttpResponse("No permission")
return HttpResponse("login")
def auth(request):
return HttpResponse("auth")小结
常用 & 必须掌握
- 传统路由
- 路由分发
- name
少用的
- 正则
- namespace
- 当前对象
【补充】关于 partial (在django路由中源码使用)
partial:俗称 偏函数,冻结函数的部分参数,返回参数更少的新函数
from functools import partial
def _xx(a1, a2):
return a1 + a2
yy = partial(_xx, a2=100)
data = yy(2)3 视图
3.1 文件or文件夹
正常一个app中只有一个 views.py 文件
当功能较复杂的时候,一个文件不够用,此时就需要 views 文件夹
apps/api
└──views
└──account
└──order
└──auth
└──urls.pyapi/urls.py
from django.urls import path
from apps.api.views import account, order
urlpatterns = [
path('login/', account.login, name="login"),
path('auth/', order.auth, name='auth'),
]
app_name = 'api'3.2 相对和绝对导入 urls
# api/urls.py
from django.urls import path
from apps.api.views import account # 绝对导入
from .views import order # 相对导入
urlpatterns = [
path('login/', account.login, name="login"),
path('auth/', order.auth, name='auth'),
]
app_name = "api"【Tip】不建议在根目录进行相对导入,Python开发时优先使用绝对导入
3.3 视图参数
urlpatterns = [
path('login/', account.login, name="login"),
path('auth/', order.auth, name='auth'),
]from django.http import HttpResponse
def login(request):
return HttpResponse("Login")request是一个对象,存放了浏览器发过来的所有内容,包含有:- 请求相关所有的数据: 当前访问的 url、请求方式、...
- django 额外添加的数据
request.path_info # 获取当前 URL
request.method # 获取请求方式:GET/POSTrequest.GET # 获取 URL 传递的参数
request.GET.get("parameter") # 获取 URL 传递的指定字段的参数
request.POST # 获取 POST 请求的全部字段
# request.POST 中被填充的前提,请求头为:
# 1. Content-Type:application/x-www-form-urlencoded
# 2. Content-Type:multipart/form-data
request.POST.get("parameter") # 获取 POST 请求的对应字段的值
request.body # 请求体(原始数据),前提是POST请求
# 若请求体为 b'v1=123&v2=456',可自动填写成字典
request.headers # 获取请求头,返回 HttpHeaders 对象(字典)
# 请求头中的 cookies
request.COOKIES # 返回(字典)客户端发送的所有 Cookie# 前提
# 请求方法为 POST/PUT/PATCH 且 enctype="multipart/form-data
request.FILES # 上传文件集合
request.FILES.get("parameter") # 取单个文件对象,键不存在返回 Nonerequest.resolver_match # Django 路由解析后的“匹配结果”对象3.4 返回值
HttpResponse返回任意字符串(HTML/JSON/文本)+ 状态码/头信息JsonResponse自动把 Python 对象序列化成 JSON 并带Content-Type: application/jsonredirect返回 302/301 重定向,让浏览器再去访问别的 URLrender加载模板 → 传参渲染 → 返回 HTML 一步到位
from django.shortcuts import HttpResponse, redirect, render
from django.http import JsonResponse
def auth(request):
pass
def login(request):
# 1.获取请求数据
print(request)
# 2.根据请求数据进行条件的判断 GET/POST GET.get("xx") POST.get("xx")
# 3.返回数据
# 3.1 字符串/字节/文本数据(图片验证码)
return HttpResponse("login")
# 3.2 JSON格式(前后端分离、app小程序后端、ajax请求)
data_dict = {"status": True, 'data': [11, 22, 33]}
return JsonResponse(data_dict)
# 3.3 重定向,不经过路由系统
return redirect("https://www.baidu.com")
return redirect("http://127.0.0.1:8000/api/auth/")
return redirect("/api/auth/") # http://127.0.0.1:8000/api/auth/
# 反向生成 URL(一般不建议)
return redirect("auth") # 根据 name 生成 URL
# 使用 reverse
from django.urls import reverse
url = reverse("auth") # 根据 name 生成 URL
return redirect(url)
# 3.4 渲染
# - a.找到 'login.html' 并读取的内容,问题:去哪里找?
# - 默认 先去settings.TEMPLATES.DIRS指定的路径找。(公共)
# - 按注册顺序在每个已注册的app中找templates目录,寻找'login.html'
# - 所以建议在app中的templates中在添加一层目录如:api/templates/api/login.html
# - 一般情况下,原则,哪个app中的的模板,去哪个那个app中寻找。
# - b.渲染(替换)得到替换后的字符串
# - c.返回浏览器
return render(request, 'api/login.html')3.5 响应头
from django.shortcuts import HttpResponse
def login(request):
res = HttpResponse("login") # 响应体
# 设置响应头
res['xx1'] = "hahaha"
res['xx2'] = "hahaha"
res['xx3'] = "hahaha"
# 设置响应头的 cookie
res.set_cookie('k1',"aaaaaaaa")
res.set_cookie('k2',"bbbbbb")
return res3.6 FBV & CBV
写视图的两种方式:
- FBV,用函数编写(目前主流)
- CBV,用类编写
# apps/api/urls.py
from django.urls import path
from apps.api.views import account
urlpatterns = [
path('login/', account.login, name='login'),
path('users/', account.UserView.as_view(), name='aaaa'),
]# apps/api/views/account.py
# 函数
from django.shortcuts import HttpResponse
def login(request):
if request.method == "GET":
res = HttpResponse("login")
return res
else:
res = HttpResponse("login POST")
return res
# 类
from django.views import View
class UserView(View):
def get(self, request): # get请求
return HttpResponse("get")
def post(self, request): # post请求
return HttpResponse("post")- 一个是函数一个是类,但是本质上是一样的,其URL 是一个函数
- 都是“接收请求对象并返回响应对象”的可调用体
4 静态资源
开发需要:css、js、图片
- 根目录的 /static/ - app目录下的 /static/ 文件夹下媒体文件:用户上传的数据(excel / pdf / video)
- 根目录的 /media/
4.1 静态文件
# /sttings.py
import os
INSTALLED_APPS = [
# 'django.contrib.admin',
# 'django.contrib.auth',
# 'django.contrib.contenttypes',
# 'django.contrib.sessions',
# 'django.contrib.messages',
'django.contrib.staticfiles',
"apps.api.apps.ApiConfig",
"apps.web.apps.WebConfig",
]
...
STATIC_URL = '/static/'
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'static'), # 添加静态文件目录
)urls.py
from django.urls import path, include
urlpatterns = [
path('', include('apps.api.urls')),
]
# apps/api/urls.py
from django.urls import path
from apps.api.views import account
urlpatterns = [
path('login/', account.login),
]
# apps\api\views\account.py
from django.shortcuts import render
def login(request):
return render(request, 'api/login.html')【Tip】需要收集文件时,要在根目录或者app的目录下手动创建 static 文件夹
查找文件顺序:根目录
static→ app中的static【问题】在删除根目录的文件后,不显示app 中的图片,浏览器200状态
解决方法:清理浏览器缓存
多app开发:文件放在各自同名的文件夹中
appname/static/appname/...
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Login</h1>
<!-- <img src="/static/api/1.png" width="400" height="225">-->
<!-- 不建议以上写法-->
<img src="{% static 'api/1.png' %}" width="400" height="225"></img>
</body>
</html>/static/api/file写死/static/,换前缀要全项目改{% load static %}+{% static 'file' %}好处:- 无需
STATICFILES_DIRS - 自动读取
STATIC_URL
- 无需
若静态文件在app之外再使用
STATICFILES_DIRS
4.2 媒体文件
需手动在根目录创建 media 文件夹
{% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>Login</h1>
<!-- <img src="/static/1.png" width="400" height="225">-->
<img src="{% static '1.png' %}" width="400" height="225"></img>
<img src="/media/123.jpg">
</body>
</html># urls.py
from django.urls import path, include
from django.conf.urls.static import static
from django.conf import settings
urlpatterns = [
path('api/', include('apps.api.urls')),
] + static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)小结
请求周期
路由系统
- 最基本路由关系
- 动态路由(含正则)
- 路由分发不同的app中 + include + 本质 + name + namespace
视图
类和函数(FBV和CBV)
参数 request
- 请求数据
- 自定义数据
响应
HttpResponse/JsonResponse/render/redirect return HttpResponse("...") 响应头 obj = HttpResponse("...") obj['xxxxx'] = "值" return obj
其他知识
虚拟环境
纯净版项目,内置app功能去掉。
多app,嵌套到apps目录。
pycharm创建django项目 + 虚拟环境
- 最新的django项目
- 低版本(环境+项目+django文件模板)
settings配置
django默认settings [先加载] 500 项目目录settings [后加载] 20静态资源
- 静态文件,项目必备【项目根目录,每个app目录下static - app注册顺序】
- 媒体文件,用户上传
5 模板
5.1 寻找html模板
# settings.py
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [os.path.join(BASE_DIR, 'templates')], # 设置模板目录,在根目录中的 templates 寻找模板
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]寻找模板的顺序:优先去项目根目录 → 每个app的 templates 中找
【Tip】如果 DIRS 不设置格外的路径,则只会在每个app中的 templates 中找
如何选择:
- 简单的项目,模板都放在根目录,在
templates创建多个 app 文件夹 - 复杂的项目,模板放在各自的app中,公共部分放在根目录的
templates
【建议】模板路径:appname/templates/appname/file 可以避免因重名的问题
【扩展】
project
└──apps
└──app01
└──templates
└──app01
└──file
└──templates
└──admin # 修改内置模块
└──file
└──app01
└──file # 顶替apps中的文件
└──app015.2 模板处理的本质
# urls.py
from django.urls import path
from apps.app01 import views
urlpatterns = [
path('index/', views.index)
]# app01/views.py
from django.shortcuts import render
def index(request):
return render(request, 'app01/index.html', {'n1': '仙人掌', 'n2': '玫瑰'}){% load static %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页app01{{n1}}</h1>
<script src="{% static 'app01/v1.js' %}"></script><!-- {{ n2 }} -->
<!--这里只能读取到 js 的静态文件,无法进行其它任何操作-->
<script>alert("{{ n2 }}")</script><!-- 玫瑰 -->
</body>
</html>步骤:
- 读取模板文件:
render(request, '模板.html', context) - 模板引擎(渲染):变量替换、标签执行、过滤器/注释/继承
- 得到纯字符串(HTML/CSS/JS 源码)
- 打包成
HttpResponse对象,返回给浏览器
5.3 常用语法
# app01/views.py
class Person:
def __init__(self, name, age):
self.name = name
self.age = age
def get_data(self):
return "def"
def fetch_data():
return "Fetch"
def gen_data():
yield 123
yield 456
yield 789
def index(request):
context = {
"n1": "仙人掌",
"n2": [11, 22, 33, 44],
"n3": {
"name": "武沛齐",
"age": 19,
},
'n4': [
{"id": 1, "name": "新晨1", "age": 18},
{"id": 2, "name": "新晨2", "age": 28},
{"id": 3, "name": "新晨3", "age": 38},
{"id": 4, "name": "新晨4", "age": 48},
],
'n5': [
Person('xinchen1', 18),
Person('xinchen2', 28),
],
'n6': fetch_data,
'n7': gen_data,
}
return render(request, 'app01/index.html', context)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页app01{{n1}}</h1>
<p>{{ n2.0 }}</p>
<p>{{ n2.2 }}</p>
<ul>
{% for item in n2 %}
<li>{{ item }}</li>
{% endfor %}
</ul>
<hr/>
<p>{{ n3.name }}</p>
<p>{{ n3.age }}</p>
<ul>
{% for k,v in n3.items %}
<li>{{ k }} = {{ v }}</li>
{% endfor %}
</ul>
<hr/>
<table border="1">
{% for item in n4 %}
<tr>
<td>{{ item.id }}</td>
<td>{{ item.name }}</td>
<td>{{ item.age }}</td>
</tr>
{% endfor %}
</table>
<hr/>
<p>{{ n5.0.name}}</p>
<p>{{ n5.1.age}}</p>
<p>{{ n5.1.get_data}}</p>
<hr/>
<p>{{ n6 }}</p>
<hr/>
<p>{{ n7 }}</p>
<ul>
{% for item in n7 %}
<li>{{ item }}</li>
{% endfor %}
</ul>
</body>
</html>image-20250908221640-q9x73da.png5.4 内置函数
# app01/views.py
from django.shortcuts import render
import datetime as datatime
def index(request):
context = {
"n1" : "仙人掌",
"n8" : "ZhangKai",
"n9" : datatime.datetime.now(),
"n10" : datatime.datetime.now().strftime("%Y-%m-%d"),
}
return render(request, 'app01/index.html', context)<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<h1>首页app01{{n1}}</h1>
<h2>{{ n8 }}</h2>
<h2>{{ n8|upper }}</h2><!--转成大写-->
<h2>{{ n8|lower }}</h2><!--转成小写-->
<h2>{{ n9 }}</h2>
<h2>{{ n9|date:"Y-m-d H:i:s" }}</h2>
<h2>{{ n10 }}</h2>
</body>
</html>5.5 自定义模板功能
确保app已在注册(settings)
在app中创建
templatetags文件夹templatetags/my_tag.py编写自定义模板功能- 自定义过滤器:
@register.filter - 自定义简单标签:
@register.simple_tag返回字符串 - 自定义包含标签:
@register.inclusion_tag('my_form.html')返回渲染好的小段模板
- 自定义过滤器:
模板里加载并使用
html{% load my_tags %} <!-- 加载模板 --> {{ con|my_filter }} <!-- 过滤器 --> {% if my_filter "参数" %}<!-- 过滤器 --> {% con "参数1" "参数2" %} <!-- 简单标签 --> {% my_form "默认值" %} <!-- 包含标签 -->
【示例】编写自定义功能
python# my_tag,py from django import template register = template.Library() @register.filter def myfunc(value): return value.upper() @register.simple_tag def mytag1(): return 'hello world' @register.simple_tag def mytag2(a1, a2): return a1 + 'hello world' + a2 @register.inclusion_tag('app01/my_form.html') def my_form(): # <h1>{{name}}--{{age}}</h1> return {'name': 'form', 'age':100} # form--100python# app01/views.py from django.shortcuts import render def index(request): context = { "n8": "ZhangKai", 'n11': [ {"id": 1, "name": "XinChen1", "age": 18}, {"id": 2, "name": "XinChen2", "age": 28}, {"id": 3, "name": "XinChen3", "age": 38}, {"id": 4, "name": "XinChen4", "age": 48}, ], } return render(request, 'app01/index.html', context)html{% load my_tag %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页app01{{n1}}</h1> <h2>{{ n8 }}</h2> <h2>{{ n8|upper }}</h2><!--转成大写--> <h2>{{ n8|lower }}</h2><!--转成小写--> <h2>{{ n8|myfunc }}</h2><!--转成小写--> <hr/> <table border="1"> {% for item in n11 %} <tr> <td>{{ item.id }}</td> <td>{{ item.name|myfunc }}</td><!--只需在这循环一次,视图函数中不需循环--> <td>{{ item.age }}</td> </tr> {% endfor %} </table> <hr/> <p>{% mytag1 %}</p> <p>{% mytag2 "my " " !" %}</p> <hr/> <p>{% my_form %}</p> </body> </html>【案例】根据用户权限不同显示不同的菜单
python# my_tag,py from django import template register = template.Library() @register.inclusion_tag('app01/menu.html') def menu(role): if role == "user": return { 'data': [ {"title":"用户管理", "url":"/xxxx/xxx"}, {"title":"账户管理", "url":"/xxxx/xxx"} ] } if role == "admin": return { 'data': [ {"title":"用户管理", "url":"/xxxx/xxx"}, {"title":"订单管理", "url":"/xxxx/xxx"}, {"title":"财务管理", "url":"/xxxx/xxx"}, {"title":"订单管理", "url":"/xxx/xxx"} ] }python# app01/views.py from django.shortcuts import render def index(request): context = { "n12": 18, } return render(request, 'app01/index.html', context)html<!-- menu.html --> <ul> {% for item in data %} <li><a href="{{ item.url }}">{{ item.title }}</a></li> {% endfor %} </ul>html<!-- index.html --> {% load my_tag %} <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> </head> <body> <h1>首页app01{{n1}}</h1> {% if n12 >= 18 %} <h4>成年人</h4> {% else %} <h4>未成年人</h4> {% endif %} <hr/> {% menu 'user' %} {% menu 'admin' %} </body> </html>
5.6 继承和母版
一般作为母版的模板,作用于多个app,故建议放在根目录的 templates
母版基础结构
html<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="公共CSS文件"> {% block css %} {% endblock %} </head> <body> <div>头部</div> {% block body %} {% endblock %} <div>尾部</div> <link rel="stylesheet" href="公共JS文件"> {% block js %} {% endblock %} </body> </html>子模板结构
html{% extends '母版.html' %}<!--继承--> {% block css %} {% endblock %} {% block body %}{% endblock %} {% block js %} {% endblock %}【示例】
python# url.py from django.urls import path from apps.app01 import views urlpatterns = [ path('index/', views.index), path('home/', views.home), path('user/', views.user) ]python# app01/views.py from django.shortcuts import render def index(request): return render(request, 'app01/index.html') def home(request): return render(request, 'app01/home.html', {'title': '首页', 'info' : '这是首页'}) def user(request): return render(request, 'app01/user.html', {'title': 'user'})html<!--layout.html--> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <link rel="stylesheet" href="公共CSS文件"> {% block css %} {% endblock %} </head> <body> <div>头部</div> {% block body %} {% endblock %} <div>尾部</div> <link rel="stylesheet" href="公共JS文件"> {% block js %} {% endblock %} </body> </html>html<!--home.html--> {% extends 'layout.html' %}<!--继承--> {% block body %} <h1>HOME</h1> <p>{{ info }}</p> {% endblock %}html<!--user.html--> {% extends 'layout.html' %}<!--继承--> {% block body %} <h1>User</h1> {% endblock %}
5.7 模板的导入
【示例】
html<!--要导入的模板--> <h1>头部:{{ title }}</h1> <h2>头部下方</h2>html{% extends 'layout.html' %}<!--继承--> {% block body %} <h1>HOME</h1> {% include 'app01/header.html' %}<!--导入模板--> <!--把模板的内容直接插入并替换变量--> <p>{{ info }}</p><!--替换变量--> {% endblock %}渲染步骤:将母版整体替换 → 替换上下文变量 → 把模板的内容直接插入并替换变量 → HTML 字符串
6 中间件
屏幕截图 2025-09-09 182234 - 副本-20250909182901-lena2rv.png类
在根目录创建
middleware文件夹,将中间件文件放入,再编写class定义方法
注册
python# settings MIDDLEWARE = [ 'middleware.md.MyMd' ] # 路径:中间件文件中类的路径
6.1 原始方式
- 最原始的编写方式
# middleware/md.py
class MyMd(object):
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print("开始执行视图函数之前")
response = self.get_response(request)
print("执行完毕视图函数之后")
return response6.2 MiddlewareMixin
正常编写
pythonfrom django.utils.deprecation import MiddlewareMixin class MyMd(MiddlewareMixin): def process_request(self, request): print("开始执行视图函数之前") def process_response(self, request, response): print("执行完毕视图函数之后") # django内部默认执行 call 方法,传入参数参考源码(面向对象、反射)
pythonfrom django.http import HttpResponse class MyMd(object): def __init__(self, get_response): self.get_response = get_response def __call__(self, request): if hasattr(self, 'process_request'): response = self.process_request(request) response = response or self.get_response(request) if hasattr(self, 'process_response'): self.process_response(request, response) return response def process_request(self, request): print("开始执行视图函数之前") request.xxx = 123 return HttpResponse("终止") def process_response(self, request, response): print("执行完毕视图函数之后") return HttpResponse("都不用")
【问题】prcess_request 执行时,是否已执行了路由匹配?
- 在完成所有
process_request之后,才会执行路由匹配 - 然后在回到最开始的中间件,再执行每一个
process_view - 再执行视图函数
from django.utils.deprecation import MiddlewareMixin
class MyMd(MiddlewareMixin):
def process_request(self, request):
print("开始执行视图函数之前")
print(request.resolver_match)# None
def process_view(self, request, view_func, view_args, view_kwargs):
print(request, view_func, view_args, view_kwargs)
# <WSGIRequest: GET '/user/'> <function user at 0x0000020BAE340670> () {}
def process_response(self, request, response):
print("执行完毕视图函数之后")
print(request.resolver_match)
# ResolverMatch(func=apps.app01.views.user, args=(), kwargs={}, url_name=None, app_names=[], namespaces=[], route=user/)
return response
def process_exception(self, request, exception): # 捕获异常,处理异常(自定义异常界面)
print("出现异常", exception)
def process_template_response(self, request, response):
# 触发条件:视图函数返回TemplateResponse对象 or 对象中含有.render方法
return response # 拦截并修改「模板响应对象」(即 TemplateResponse)【Tip】process_view 在django源码中写死
7 ORM
orm,关系对象映射
通过编写 class 生成数据库中的表,以及操作类中的对象(表数据)
本质:翻译(装换)的过程
特点:开发效率高、执行效率低
image-20220703155844071-20250830193045-9x681vm.png7.1 编写ORM操作的步骤
连接数据库
python# settings.py DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', # 'NAME': os.path.join(BASE_DIR, 'db.sqlite3'), 'NAME': BASE_DIR / 'db.sqlite3', } }注册app
python# settings.py INSTALLED_APPS = [ ... 'app_name.apps.AppNameConfig', # 配置类 ]编写
models.类python# models.py(app中) from django.db import models class TableName(models.Model): .... ....生成 & 执行迁移
根据
models.py在appname/migrations中生成一个对数据库操作的配置文件pythonpython manage.py makemigrations # 生成迁移文件读取已注册app中的
migrations目录,将配置文件转换成SQL(生成表、修改表),并连接数据库运行pythonpython manage.py migrate # 真正建表
7.2 表结构
实现:创建表、修改表、删除表
在app中的
models.py中按照规则编写class===> 表结构编写类
pythonfrom django.db import models class UserInfo(models.Model): name = models.CharField(max_length=16) age = models.IntegerField()- 生成表名:
appname_classname(类名小写,如app01_userinfo)
- 生成表名:
【Tip】不要再手动去修改数据的表结构 + 时刻保证 ORM 和 数据表是对应
1)常见字段和参数
字段
pythonCharField (字符串) TextField (长文本) 如果字符很多最好存储在文件中 IntegerField (整型) BigIntegerField (长整型) SmallIntegerField (短整型) PositiveIntegerField (正整型) PositiveBigIntegerField (正长整型) DateField (日期) DateTimeField (时间) BooleanField (布尔值) MySQL没有Bool,借助SmallIntegerField创造出来:0/1 DecimalField (小数)参数
pythonname = models.CharField(verbose_name="姓名", max_length=16, default="默认值", null=True, blank=True, db_index=True, unique=True, choices=(("sh", "上海"), ("bj", "北京")) ) # max_length 容纳字符数量 age = models.IntegerField(verbose_name="年龄", default=1, db_index=True, unique=True, null=True, blank=True, choices=((1, "小于18"), (2, "18-25"), (3, "25-30")) ) # IntegerField、BigIntegerField、SmallIntegerField、PositiveIntegerField、PositiveSmallIntegerField 都是整型参数一致 # CharField 与 IntegerField 参数的区别: IntegerField 没有 max_length, choices 存储整型 create_data = models.DateField(verbose_name="日期", auto_now=True) xcreate_data = models.DateTimeField(verbose_name="时间") # auto_now=True 调用 .save() 时自动添加当前时间 active = models.BooleanField(verbose_name="是否激活") price = models.DecimalField(verbose_name="价格", max_digits=5, decimal_places=2) # max_digits 最大位数 # decimal_places 小数位数 ''' verbose_name 备注名 default 默认值 null 数据库能否为空,官方建议尽量避免 null=True 在 CharField/TextField/BooleanField 使用 blank 用户填写的数据(页面)能否为空 db_index 添加索引,查询方便 unique 创建唯一索引 choices 添加选项(数据库存储 sh、bj,页面显示 上海、北京) '''【案例】用户表、商品表
pythonfrom django.db import models class UserInfo(models.Model): name = models.CharField(verbose_name="姓名", max_length=16, db_index=True) age = models.PositiveIntegerField(verbose_name="年龄") email = models.CharField(verbose_name="邮箱", max_length=128, unique=True) amount = models.DecimalField(verbose_name="余额", max_digits=10, decimal_places=2, default=0) register_date = models.DateField(verbose_name="注册时间", auto_now=True) class Goods(models.Model): title = models.CharField(verbose_name="标题", max_length=32) # detail = models.CharField(verbose_name="详细信息", max_length=255) detail = models.TextField(verbose_name="详细信息") price = models.PositiveIntegerField(verbose_name="价格") count = models.PositiveBigIntegerField(verbose_name="库存", default=0)
2)表关系
- 单表:不与其它表存在关系
- 一对多
- 多对多
- 一对一
# ForeignKey
models.ForeignKey(
to=Department, # 必填:模型类名(关联表)或 'app.Model' 字符串
on_delete=models.CASCADE, # 必填:关联对象被删时
# 1. CASCADE 级联删除
# 2. SET_NULL 设置为 null
# 3. SET_DEFAULT 设置为 默认值
related_name='employees', # 反向查询名(默认 模型_set)
null=True, blank=True, # on_delete=models.SET_NULL 必写
db_index=True, # 添加索引
verbose_name='可读字段名', # 可读字段名
)【案例1】一对多
部门表 id title 1 销售 2 运营 3 新媒体 用户表 id name depart_id 1 张弛 1 2 周叶 1 3 张三 2 4 李四 3 pythonfrom django.db import models class Department(models.Model): """部门表""" # id = models.BigAutoField(primary_key=True) # 默认添加 id title = models.CharField(verbose_name="标题", max_length=32) class Meta: db_table = "department" # 自定义表名,默认为 appname_department class UserInfo(models.Model): """用户表""" # id,name,depart_id name = models.CharField(verbose_name="用户名", max_length=16) # depart_id -> type:bigint depart = models.ForeignKey(verbose_name="部门ID", to="Department", # 关联的表 to_field="id", # 指定关联的表字段 on_delete=models.SET_NULL, null=True, blank=True # on_delete 删除关联的表数据时,本表数据如何处理 # 1. CASCADE 级联删除 # 2. SET_NULL 设置为 null # 3. SET_DEFAULT 设置为 默认值 ) # 建表默认添加 id ,不写 to_field 的话就会默认匹配 主键id # 若 on_delete=models.SET_NULL 基本都要设置 null=True, blank=True【案例2】多对多
男生表 id name 1 石军 2 老张 3 乔布斯 女生表 id name 1 凤姐 2 奶茶妹 3 吉娃 关系表 id bid gid 1 1 1 2 1 2 3 1 3 4 2 1 5 3 1 6 pythonfrom django.db import models class Boy(models.Model): """男孩表""" name = models.CharField(verbose_name="姓名", max_length=16) class Girl(models.Model): """女孩表""" name = models.CharField(verbose_name="姓名", max_length=16) class B_G(models.Model): """男孩女孩关系表""" boyID = models.ForeignKey(verbose_name="男孩ID", to="Boy", to_field="id", on_delete=models.CASCADE) girlID = models.ForeignKey(verbose_name="女孩ID", to="Girl", to_field="id", on_delete=models.CASCADE)- django 中的特殊写法
pythonclass Boy(models.Model): """男孩表""" name = models.CharField(verbose_name="姓名", max_length=16) class Girl(models.Model): """女孩表""" name = models.CharField(verbose_name="姓名", max_length=16) relation = models.ManyToManyField(verbose_name="男女关系", to="Boy") # ManyToManyField 多对多, 自动生成关联表 # 只能生成 id、bid、gid【Tip】实际开发中使用
ManyToManyField生成表,没有自己编写表直观一对一
python表,100列 -> 50A表 50B表 博客园为例: - 注册,用户名、密码,无法创建博客 - 开通博客 地址/pythonfrom django.db import models class UserInfo(models.Model): name = models.CharField(verbose_name="用户名", max_length=32, db_index=True) pwd = models.CharField(verbose_name="密码", max_length=32) class Blog(models.Model): # user = models.ForeignKey(to="UserInfo", on_delete=models.CASCADE, unique=True) user = models.OneToOneField(to="UserInfo", on_delete=models.CASCADE) blog = models.CharField(verbose_name="博客地址", max_length=255) summary = models.CharField(verbose_name="简介", max_length=128)
【Tip】设计项目表结构:表名和字段都不要拼音
7.3 连接数据库
连接 SQL 类
python# 默认 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.sqlite3', 'NAME': BASE_DIR / 'db.sqlite3', } }连接 MySQL
需先在 MySQL 创建数据库
python# settings DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'database_name', # 数据库名字 'USER': 'root', 'PASSWORD': 'root123', 'HOST': '127.0.0.1', # ID 'PORT': 3306, } }安装第三方组件
pymysql(建议使用)
pythonpip install pymysqlpython# project/project/__init__.py import pymysql pymysql.install_as_MySQLdb()mysqlclient(mysqlclient的分支)需要提前安装 mysql 或 mysql-client
pythonpip install mysqlclient
连接其它数据库
python# pip install psycopg2 DATABASES = { 'default': { 'ENGINE': 'django.db.backends.postgresql', 'NAME': 'mydatabase', 'USER': 'mydatabaseuser', 'PASSWORD': 'mypassword', 'HOST': '127.0.0.1', 'PORT': 5432, } }python# pip install cx-Oracle DATABASES = { 'default': { 'ENGINE': 'django.db.backends.oracle', 'NAME': "xxxx", # 库名 "USER": "xxxxx", # 用户名 "PASSWORD": "xxxxx", # 密码 "HOST": "127.0.0.1", # ip "PORT": 1521, # 端口 } }
7.4 连接池
django默认内置没有数据库连接池
pymysql --> 操作数据库
DBUtils --> 连接池- 连接池组件
https://pypi.org/project/django-db-connection-pool/
pip install django-db-connection-poolDATABASES = {
"default": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'database_name',# 数据库名字
'USER': 'root',
'PASSWORD': 'root123',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最少
'MAX_OVERFLOW': 10, # 最多增加10个,即:最多20个
'RECYCLE': 24 * 60 * 60, # 连接可以被重复用多久,超过会重新创建,-1表示永久。
'TIMEOUT':30, # 池中没有连接,最多等待的时间
}
}
}【Tip】该组件基于 SQLAlchemy
7.5 多数据库
django支持项目连接多个数据库
DATABASES = {
"default": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'day05_database',# 数据库名字
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最少
'MAX_OVERFLOW': 10, # 最多增加10个,即:最多20个
'RECYCLE': 24 * 60 * 60, # 连接可以被重复用多久,超过会重新创建,-1表示永久。
'TIMEOUT':30, # 池中没有连接,最多等待的时间
}
},
"db_day05": {
'ENGINE': 'dj_db_conn_pool.backends.mysql',
'NAME': 'day05_database_1',# 数据库名字
'USER': 'root',
'PASSWORD': '123456',
'HOST': '127.0.0.1', # ip
'PORT': 3306,
'POOL_OPTIONS': {
'POOL_SIZE': 10, # 最少
'MAX_OVERFLOW': 10, # 最多增加10个,即:最多20个
}
}
}1)读写分离
192.168.1.2 default master [写]
组件
192.168.2.12 db_day05 slave [读]生成数据库表
bashpython manage.py makemigrations python manage.py migrate # 只连接默认数据库 default python manage.py migrate --database=default python manage.py migrate --database=db_day05 # 非默认数据库需手动连后续开发
python# app01.views.py from app01 import models def index(request): models.UserInfo.objects.using("default").create(title='yuyu') # 创建(写)数据 # using() 指定数据库 models.UserInfo.objects.using("db_day05").all() # 查询(读)数据编写
router类,简化【后续开发】pythonclass DemoRouter(object): def db_for_read(...): return "db_day05" def db_for_write(...): return "default"router = ["DemoRouter"]【示例】
bash# settings.py DATABASE_ROUTERS = ['utils.router.DemoRouter']python# app01/views.py from django.shortcuts import HttpResponse from app01 import models def index(request): models.UserInfo.objects.create(title='yuyu')# 创建(写)数据 res = models.UserInfo.objects.all() # 查询(读)数据 print(res) return HttpResponse("OK")python# root/utils/router.py class DemoRouter(object): def db_for_read(self, model, **hints): print("read:") print(model._meta.app_label) # app01 print(model._meta.model_name) # userinfo print(hints) # {} if model._meta.app_label == "userinfo": return "db_day05" return "default" def db_for_write(self, model, **hints): print("write:") print(model._meta.app_label) # app01 print(model._meta.model_name) # userinfo print(hints) # {} return "default"
2)分库(多个app -> 多数据库)
100张表,50表-A数据库【app01】;50表-B数据库【app02】
# app01/models.py
from django.db import models
class UserInfo(models.Model):
title = models.CharField(verbose_name="标题", max_length=32)# app02/models.py
from django.db import models
class UserInfo(models.Model):
title = models.CharField(verbose_name="标题2", max_length=32)python manage.py makemigrations
python manage.py migrate app01 --database=default
python manage.py migrate app02 --database=db_day05image-20250912155827-5i066n4.pngimage-20250912155838-630f06g.png读写操作
python# app01/models.py from django.db import models class UserInfo(models.Model): title = models.CharField(verbose_name="标题", max_length=32) # app02/models.py from django.db import models class Role(models.Model): title = models.CharField(verbose_name="标题2", max_length=32)python# app01/views.py from django.shortcuts import HttpResponse from app01 import models as m1 from app02 import models as m2 def index(request): # app01中的操作 -> default v1 = m1.UserInfo.objects.all() print(v1) # app02中的操作 -> db_day05 v2 = m2.Role.objects.using('db_day05').all() print(v2) return HttpResponse("返回")routerpython# app01/views.py from django.shortcuts import HttpResponse from app01 import models as m1 from app02 import models as m2 def index(request): # app01中的操作 -> default v1 = m1.UserInfo.objects.all() print(v1) # app02中的操作 -> db_day05 v2 = m2.Role.objects.all() print(v2) return HttpResponse("返回")python# root/utils/router.py class DemoRouter(object): def db_for_read(self, model, **hints): if model._meta.app_label == "app01": return "default" if model._meta.app_label == "app02": return "db_day05" def db_for_write(self, model, **hints): if model._meta.app_label == "app01": return "default" if model._meta.app_label == "app02": return "db_day05"pythonpython manage.py makemigrations python manage.py migrate app01 --database=default python manage.py migrate app02 --database=db_day05
3)分库(单app)
100张表,50表-A数据库;50表-B数据库
【Tip】必须借助 router
# app01/models.py
from django.db import models
# defult
class UserInfo(models.Model):
title = models.CharField(verbose_name="标题1", max_length=32)
# db_day05
class Role(models.Model):
title = models.CharField(verbose_name="标题2", max_length=32)
# db_day05
class Depart(models.Model):
title = models.CharField(verbose_name="标题3", max_length=32)# app01/views.py
from django.shortcuts import HttpResponse
from app01 import models as m1
def index(request):
res = m1.UserInfo.objects.all()
print(res)
return HttpResponse("返回")# router.py
class DemoRouter(object):
def allow_migrate(self, db, app_label, model_name=None, **hints):
if db == "db_day05":
if model_name in ["role", "depart"]:
return True
else:
return False
if db == "default":
if model_name in ["userinfo"]:
return True
else:
return False
# return: True - 迁移到该数据库(python manage.py migrate)
# return: False - 不迁移python manage.py makemigrations
python manage.py migrate app01 --database=default
python manage.py migrate app01 --database=db_day05读写操作
python# app01/models.py from django.db import models # defult class UserInfo(models.Model): title = models.CharField(verbose_name="标题1", max_length=32) # db_day05 class Role(models.Model): title = models.CharField(verbose_name="标题2", max_length=32) # db_day05 class Depart(models.Model): title = models.CharField(verbose_name="标题3", max_length=32)python# app01/views.py from django.shortcuts import HttpResponse from app01 import models as m1 def index(request): # app01中的操作 -> default v1 = m1.UserInfo.objects.all() print(v1) # app02中的操作 -> db_day05 v2 = m1.Role.objects.all() print(v2) return HttpResponse("返回")python# router.py class DemoRouter(object): def db_for_read(self, model, **hints): if model._meta.model_name == ["role", "depart"]: return "db_day05" if model._meta.model_name == ["userinfo"]: return "default" def db_for_write(self, model, **hints): if model._meta.model_name == ["role", "depart"]: return "db_day05" if model._meta.model_name == ["userinfo"]: return "default" def allow_migrate(self, db, app_label, model_name=None, **hints): if db == "db_day05": if model_name in ["role", "depart"]: return True else: return False if db == "default": if model_name in ["userinfo"]: return True else: return False # return: True - 迁移到该数据库(python manage.py migrate) # return: False - 不迁移
4)注意事项
分库,表拆分到不同数据库
不要跨数据库做关联 -> django不支持 怎么办? 尽可能的将有关联的表放在一个库中为什么表拆分到不同的库?
用户量大时,可以将数据拆分到不同数据库(服务器)中,减轻服务端的压力
7.6 数据操作
实现:增删改查
1)单表
# models.py
from django.db import models
class Role(models.Model):
title = models.CharField(verbose_name="姓名", max_length=32)
od = models.IntegerField(verbose_name="排序", default=0)
def __str__(self):
return "{}-{}".format(self.id, self.title)# views.py
# 创建
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
obj1 = models.Role.objects.create(title='管理员', od=1)
obj2 = models.Role.objects.create(title='普通用户', od=2)
obj3 = models.Role.objects.create(**{'title' : '普通用户', 'od' : 1})
print(obj1)
print(obj1.id, obj1.title, obj1.pk)
print(obj2, obj3)
# 内存 -> 数据库
obj4 = models.Role(title="用户1", od=1)
obj4.od = 7
obj4.save() # 保存到数据库
obj5 = models.Role(**{'title': '普通用户', 'od': 1})
obj5.save()
return HttpResponse("返回")
# 删除
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
data1 = models.Role.objects.all()
data1.delete()
data2 = models.Role.objects.all().delete()
data3 = models.Role.objects.filter(title='管理员')
data3.delete()
data4 = models.Role.objects.filter(id=1).delete()
return HttpResponse("返回")
# 修改
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
models.Role.objects.update(od=100)
models.Role.objects.filter(title='管理员').update(od=10, id=1)
models.Role.objects.filter(title='管理员').update(**{'od': 10, 'id': 1})
return HttpResponse("返回")
# 查询
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
al = models.Role.objects.all() # 返回QuerySet对象,里面存储的是模型对象
for obj in al:
print(obj, obj.id, obj.title, obj.od)
fil = models.Role.objects.filter(od=100, id=1) # QuerySet=[obj, obj, ...]
fil = models.Role.objects.filter(**{'od': 100, 'id': 1})
for obj in fil:
print(obj, obj.id, obj.title, obj.od)
models.Role.objects.filter(id=1) # where id=1
models.Role.objects.filter(id__gt=1) # where id>1
models.Role.objects.filter(id__gte=1) # where id>=1
models.Role.objects.filter(id__lt=1) # where id<1
models.Role.objects.filter(id__lte=1) # where id<=1
models.Role.objects.filter(id__in=[1, 2, 3]) # where id in [1, 2, 3]
models.Role.objects.filter(id__range=[1, 3]) # where id between 1 and 3
models.Role.objects.filter(title__contains='管') # where title like '%管%'
models.Role.objects.filter(title__startswith='管') # where title like '管%'
models.Role.objects.filter(title__isnull=True) # where title is null
models.Role.objects.exclude(id=1) # where id!=1
models.Role.objects.exclude(id=1).filter(od=100) # where id!=1 and od=100
models.Role.objects.filter(id__gt=1) # QuerySet=[obj, obj, ...]
models.Role.objects.filter(id__gt=1).values('title', 'od') # QuerySet=[{}, {}]
models.Role.objects.filter(id__gt=1).values_list('title', 'od') # QuerySet=[(), ()]
models.Role.objects.filter(id__gt=1).first() # QuerySet=[obj]
models.Role.objects.filter(id__gt=1).last()
models.Role.objects.filter(id__gt=1).exists() # exists: bool
models.Role.objects.filter(id__gt=1).order_by("id") # asc
models.Role.objects.filter(id__gt=1).order_by("-id") # desc
models.Role.objects.filter(id__gt=1).order_by("id", "-od")
return HttpResponse("返回")2)一对多
# models.py
class Depart(models.Model):
title = models.CharField(verbose_name="部门", max_length=32)
class Admin(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32)
pwd = models.CharField(verbose_name="密码", max_length=32)
depart = models.ForeignKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE, related_name="d1")
new_depart = models.ForeignKey(verbose_name="部门", to="Depart", on_delete=models.CASCADE, related_name="d2")# views.py
# 创建
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
models.Depart.objects.create(title='部门1')
models.Admin.objects.create(name='yuyu1', pwd='123456', depart_id=1)
models.Admin.objects.create(**{'name': 'yuyu1', 'pwd': '123456', 'depart_id': 1})
obj = models.Depart.objects.filter(id=1).first()
models.Admin.objects.create(name='yuyu2', pwd='123456', depart=obj)
return HttpResponse("返回")
# 删除
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
models.Admin.objects.filter(id=1).delete()
# 删除 部门ID为 1 的所有成员
models.Admin.objects.filter(depart_id=1).delete()
# 删除 部门1 中的所有成员
obj = models.Depart.objects.filter(title='部门1').first()
models.Admin.objects.filter(depart=obj).delete()
# 跨表查询 删除 部门1 中的所有成员
models.Admin.objects.filter(depart__title='部门1').delete()
return HttpResponse("返回")
# 查询
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
models.Admin.objects.all() # QuerySet=[obj, obj, ...]
# 跨表查询
models.Admin.objects.all().values('name', 'depart__title')
models.Depart.objects.all().values('title', 'd1__name', 'd2__name')
# select * from admin
v1 = models.Admin.objects.filter(id__gt=2)
for obj in v1:
print(obj, obj.name, obj.depart_id)
# select * from admin inner join depart
v2= models.Admin.objects.filter(id__gt=2).select_related('depart') # select_related() 优化查询
for obj in v2:
print(obj, obj.name, obj.depart_id, obj.depart.title)
# queryset=[{},{}]
models.Admin.objects.filter(id__gt=2).values('name', 'depart__title')
# queryset=[(),()]
models.Admin.objects.filter(id__gt=2).values_list('name', 'depart__title')
return HttpResponse("返回")
# 更新
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
models.Admin.objects.filter(id=1).update(name='yuyu10', pwd='123123')
models.Admin.objects.filter(id=1).update(**{'name': 'yuyu10', 'pwd': '123123'})
# models.Admin.objects.filter(id=1).update(depart__title='部门10') # 不能跨表更新
return HttpResponse("返回")3)多对多
# models.py
class Boy(models.Model):
name = models.CharField(verbose_name="姓名", max_length=16, db_index=True)
class Girl(models.Model):
name = models.CharField(verbose_name="姓名", max_length=16, db_index=True)
class B_G(models.Model):
boyID = models.ForeignKey(to="Boy", on_delete=models.CASCADE)
girlID = models.ForeignKey(to="Girl", on_delete=models.CASCADE)
address = models.CharField(verbose_name="地址", max_length=32)# views.py
from django.shortcuts import HttpResponse
from app01 import models
def index(request):
# 创建
models.Boy.objects.bulk_create(
objs=[models.Boy(name='gg1'), models.Boy(name='gg2'), models.Boy(name='gg3')]
)
models.Girl.objects.bulk_create(
objs=[models.Girl(name='mm1'), models.Girl(name='mm2'), models.Girl(name='mm3')]
)
models.B2G.objects.bulk_create(
objs=[models.B2G(address='地址1', boyID_id=1, girlID_id=3),
models.B2G(address='地址2', boyID_id=2, girlID_id=2),
]
)
g_obj = models.Girl.objects.filter(id=1).first()
b_obj = models.Boy.objects.filter(id=1).first()
models.B2G.objects.create(address='地址1', boyID=b_obj, girlID=g_obj)
# 查询
v1 = models.B2G.objects.filter(boyID__name='gg1').select_related('girlID') # queryset=[obj, obj, ...]
for obj in v1:
print(obj.address, obj.boyID.name, obj.girlID.name)
v2 = models.B2G.objects.filter(boyID__name='gg1').values('id', 'boyID__name', 'girlID__name') # queryset=[{}, {}, ...]
for obj in v2:
print(obj['id'], obj['boyID__name'], obj['girlID__name'])
# 删除
models.B2G.objects.filter(id=1).delete()
models.Boy.objects.filter(id=1).delete()
return HttpResponse("返回")4)一对一
# models.py
class UserInfo(models.Model):
name = models.CharField(verbose_name="姓名", max_length=32, db_index=True)
pwd = models.CharField(verbose_name="密码", max_length=32)
class Blog(models.Model):
user = models.OneToOneField(to="UserInfo", on_delete=models.CASCADE)
blog = models.CharField(verbose_name="博客地址", max_length=255)
summary = models.CharField(verbose_name="简介", max_length=128)# views.py
rom django.shortcuts import HttpResponse
from app01 import models
def index(request):
models.UserInfo.objects.create(name='yuyu1', pwd='123456')
models.UserInfo.objects.bulk_create(
objs=[models.UserInfo(name='yuyu3', pwd='123456'),
models.UserInfo(name='yuyu4', pwd='123456')
]
)
models.Blog.objects.create(blog='www.xxx.com', summary='xxxxx', user_id=2)
user_object = models.UserInfo.objects.filter(name='yuyu1').first()
models.Blog.objects.create(blog='www.yyy.com', summary='yyyyy', user=user_object)
user_object = models.UserInfo.objects.filter(name='yuyu1').first()
print(user_object.name, user_object.pwd, user_object.blog)
print(user_object.blog.blog, user_object.blog.summary)
blog_object = models.Blog.objects.filter(id=1).select_related("user").first()
print(blog_object.blog, blog_object.summary, blog_object.user.name, blog_object.user.pwd)
models.UserInfo.objects.filter(name="yuyu2").values("name", 'pwd', 'blog__blog', "blog__summary")
return HttpResponse("返回")8 cookie 和 session
8.1 cookie
from django.shortcuts import HttpResponse
def login(request):
res = HttpResponse("...")
res.set_cookie("v1","123123",
max_age=100, # 有效期: 100秒
path='/', # 路径: 该路径下的所有请求中都携带cookie
domain='.baidu.com', # 域名: 该域名下的所有请求中都携带cookie
secure=False, # Ture,https传输时才携带cookie
httponly=False) # True,只允许通过http协议访问cookie,其它方式无法访问cookie(如 js)
return res
def home(request):
print(request.COOKIES) # {'v1': '123123'}
return HttpResponse("Home")8.2 配置session
文件版
python# settings MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # session SESSION_ENGINE = 'django.contrib.sessions.backends.file' SESSION_FILE_PATH = 'xxxx' # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址 temofile.gettempdir() SESSION_COOKIE_NAME = "sid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https传输cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存pythonfrom django.shortcuts import HttpResponse def login(request): # 设置 session + cookie request.session['user_info'] = 'yuyu' return HttpResponse("登录") def home(request): print(request.session.get('user_info')) return HttpResponse("Home")数据库版
python# settings INSTALLED_APPS = [ 'django.contrib.sessions', 'django.contrib.staticfiles', 'app01.apps.App01Config', ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # session SESSION_ENGINE = 'django.contrib.sessions.backends.db' # SESSION_FILE_PATH = '/' # 缓存文件路径,如果为None,则使用tempfile模块获取一个临时地址 temofile.gettempdir() SESSION_COOKIE_NAME = "sid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https传输cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存pythonpython manage.py makemigrations python manage.py migrate缓存
python# settings INSTALLED_APPS = [ 'django.contrib.staticfiles', "app01.apps.App01Config", ] MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ] # session SESSION_ENGINE = 'django.contrib.sessions.backends.cache' SESSION_CACHE_ALIAS = 'default' SESSION_COOKIE_NAME = "sid" # Session的cookie保存在浏览器上时的key,即:sessionid=随机字符串 SESSION_COOKIE_PATH = "/" # Session的cookie保存的路径 SESSION_COOKIE_DOMAIN = None # Session的cookie保存的域名 SESSION_COOKIE_SECURE = False # 是否Https传输cookie SESSION_COOKIE_HTTPONLY = True # 是否Session的cookie只支持http传输 SESSION_COOKIE_AGE = 1209600 # Session的cookie失效日期(2周) SESSION_EXPIRE_AT_BROWSER_CLOSE = False # 是否关闭浏览器使得Session过期 SESSION_SAVE_EVERY_REQUEST = True # 是否每次请求都保存Session,默认修改之后才保存
9 缓存
服务器 +
redisdjango
安装连接
redispythonpip install django-redissettings.pypython# redis 缓存 CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100} # "PASSWORD": "密码", } } }手动操作
redispythonfrom django_redis import get_redis_connection conn = get_redis_connection("default") conn.set("xx","123123") conn.get("xx")
引用:
- 模块6:Django 实战开发
Django 项目规范
Django 框架
原型级
my_project/ # 项目根目录
├── manage.py # 项目管理脚本
├── my_project/ # 主项目配置目录
│ ├── __init__.py
│ ├── settings.py # 全局配置文件
│ ├── urls.py # 全局路由配置
│ ├── wsgi.py # WSGI部署接口
│ └── asgi.py # ASGI异步部署接口
├── app1/ # 应用1
│ ├── migrations/ # 数据库迁移文件
│ ├── __init__.py
│ ├── admin.py # 后台管理配置
│ ├── apps.py # 应用配置
│ ├── models.py # 数据模型
│ ├── tests.py # 单元测试
│ ├── views.py # 视图函数/类
│ └── urls.py # 子路由(需手动创建)
├── app2/ # 应用2((结构同app1)
│ ├── ...
├── templates/ # HTML模板目录
│ └── base.html # 基础模板
├── static/ # 静态文件(CSS/JS/图片)
├── media/ # 媒体文件
├── venv/ # 虚拟环境
├── .gitignore # 忽略venv, *.pyc, __pycache__等
└── requirements.txt # 单文件依赖团队级
需要区分开发/生产环境,应用数3-5个
环境隔离,初步解耦,支持并行开发
my_project/
├── manage.py
│
├── my_project/ # 主配置目录
│ ├── __init__.py
│ ├── settings.py # 主配置文件(基础配置)
│ ├── local_settings.py # 本地配置(**不提交Git**,逐步淘汰方案)
│ ├── urls.py # 全局路由
│ ├── wsgi.py
│ └── asgi.py
│
├── app1/ # 应用结构增强
│ ├── migrations/
│ ├── templates/app1/ # ✅ 应用专属模板(命名空间防冲突)
│ │ └── example.html
│ ├── static/app1/ # ✅ 应用专属静态文件(可选)
│ │ └── css/style.css
│ ├── tests.py # 测试仍集中,但开始考虑拆分
│ ├── signals.py # ✅ 新增:信号处理器(按需)
│ └── urls.py # 子路由
├── app2/ # 结构同app1
│ └── ...
│
├── templates/ # 全局公共模板
│ ├── base.html
│ └── includes/ # ✅ 新增:模板片段(可复用组件)
│ ├── header.html
│ └── footer.html
│
├── static/ # 全局静态文件
│ ├── css/
│ ├── js/
│ └── images/
├── media/ # 媒体文件
│
├── common/ # ✅ 新增:公共组件
│ ├── __init__.py
│ ├── utils.py # 通用工具函数
│ └── middleware.py # 自定义中间件
│
├── requirements/ # ✅ 新增:依赖拆分
│ ├── base.txt # 基础依赖(Django, Pillow)
│ └── local.txt # 开发环境额外依赖(django-debug-toolbar)
│
├── logs/ # ✅ 新增:日志目录(建议.gitignore)
│ └── django.log
│
├── .gitignore # 增强:忽略local_settings.py, logs/等
└── README.md # 项目说明生产级结构
应用数>5个,需要CI/CD、自动化测试、长期维护
完全解耦、可测试、可监控、可扩展
my_project/
├── manage.py
│
├── my_project/ # 主配置包
│ ├── __init__.py
│ ├── settings/ # ✅ 配置目录化(环境完全隔离)
│ │ ├── __init__.py
│ │ ├── base.py # 公共配置(继承基类)
│ │ ├── development.py # 开发环境(SQLite, DEBUG=True)
│ │ └── production.py # 生产环境(PostgreSQL, Sentry)
│ │
│ ├── urls/ # ✅ 路由目录化
│ │ ├── __init__.py
│ │ ├── base.py # 主路由(admin, home)
│ │ ├── api.py # API路由(DRF)
│ │ └── debug.py # 调试路由(debug-toolbar)
│ │
│ ├── wsgi.py
│ ├── asgi.py
│ ├── celery.py # ✅ Celery异步任务配置(预留)
│ └── routing.py # ✅ Channels WebSocket路由(预留)
│
├── apps/ # ✅ 应用统一目录(关键解耦)
│ ├── __init__.py
│ │
│ ├── users/ # 用户应用(带命名空间)
│ │ ├── migrations/
│ │ ├── management/ # ✅ 自定义命令
│ │ │ └── commands/
│ │ │ └── seed_users.py
│ │ ├── templates/users/ # App专属模板
│ │ ├── static/users/ # App专属静态文件
│ │ ├── fixtures/ # ✅ 测试/初始数据
│ │ │ └── test_users.json
│ │ ├── tests/ # ✅ 测试拆分(按职责)
│ │ │ ├── __init__.py
│ │ │ ├── test_models.py # 模型测试
│ │ │ ├── test_views.py # 视图测试
│ │ │ └── test_api.py # API测试
│ │ │
│ │ ├── apps.py # name='apps.users'(关键)
│ │ ├── models.py
│ │ ├── views.py
│ │ ├── urls.py # 子路由
│ │ ├── api.py # ✅ DRF视图集
│ │ ├── serializers.py # ✅ DRF序列化器
│ │ ├── permissions.py # ✅ 自定义权限
│ │ └── signals.py # 信号处理器
│ │
│ └── blog/ # 博客应用(结构同users)
│ └── ...
│
├── common/ # 公共组件(模块化)
│ ├── __init__.py
│ ├── middleware/ # ✅ 按功能拆分
│ │ ├── __init__.py
│ │ ├── logging.py # 请求日志
│ │ └── auth.py # 认证中间件
│ │
│ ├── utils/ # ✅ 按用途拆分
│ │ ├── __init__.py
│ │ ├── validators.py # 表单验证
│ │ └── paginator.py # 分页封装
│ │
│ ├── exceptions/ # ✅ 自定义异常
│ │ └── custom.py
│ └── constants.py # 常量定义
│
├── templates/ # 全局模板
│ ├── base.html
│ ├── includes/ # 模板片段
│ │ ├── header.html
│ │ ├── footer.html
│ │ └── pagination.html
│ └── errors/ # ✅ 错误页面(400/403/404/500)
│ ├── 404.html
│ └── 500.html
│
├── static/ # 全局静态文件
│ ├── css/
│ ├── js/
│ ├── images/
│ └── vendor/ # ✅ 第三方库(jQuery, Bootstrap)
│ └── bootstrap.min.css
│
├── media/ # 用户上传文件
│ ├── avatars/ # 分类存储
│ └── uploads/
│
├── logs/ # 日志目录(必须.gitignore)
│ ├── django.log
│ ├── celery.log
│ └── debug.log
│
├── tests/ # ✅ 全局集成测试
│ ├── __init__.py
│ ├── conftest.py # pytest配置
│ └── test_integration.py # 端到端测试
│
├── requirements/ # 依赖管理
│ ├── base.txt
│ ├── development.txt
│ └── production.txt # 生产依赖(gunicorn, psycopg2)
│
├── .gitignore # 完整配置
├── README.md # 项目说明
└── pyproject.toml # ✅ 代码规范(Black/isort)Django 项目部署
以下步骤基于 window + CentOS 7.6(云服务器)+ gitee
主要流程

将项目部署到服务器上的大致流程:
- 将代码从 本地 上传到 服务器
- 在服务器上 安装服务、配置环境、获取代码
- 启动服务
上传代码的方式有很多种,例如:FTP工具、scp命令、rsync服务、svn等,不过目前公司主流的都是使用 git + 代码托管平台

- 本地电脑,安装git + git命令 上传代码
- git 代码托管仓库,创建仓库
- 远端服务器,安装git + git命令 获取代码
代码管理
代码结构
bash# 服务器(部分) /data/project/<project_name>/ └── <project_name> └── settings.py └── local_settings.py └── wsgi.py └── shell └── <project_name>_uwsgi.ini └── reboot.sh └── stop.sh .gitignore manage.py requirements.txt代码托管仓库
本地电脑
安装 git
本地git配置 - 全局
git config --global user.name "xxx" git config --global user.email "xxx@xxx.com"gitignore
.gitignore文件,写入文件名或文件夹,可以git忽略一些文件,不进行版本控制GitHub 的
.gitignore文件模板集合:https://github.com/github/gitignorepython 中需要忽略的
powershell# Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] *$py.class # C extensions *.so # Distribution / packaging .Python build/ develop-eggs/ dist/ downloads/ eggs/ .eggs/ lib/ lib64/ parts/ sdist/ var/ wheels/ share/python-wheels/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .nox/ .coverage .coverage.* .cache nosetests.xml coverage.xml *.cover *.py.cover .hypothesis/ .pytest_cache/ cover/ # Translations *.mo *.pot # Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journal # Flask stuff: instance/ .webassets-cache # Scrapy stuff: .scrapy # Sphinx documentation docs/_build/ # PyBuilder .pybuilder/ target/ # Jupyter Notebook .ipynb_checkpoints # IPython profile_default/ ipython_config.py # pyenv # For a library or package, you might want to ignore these files since the code is # intended to run in multiple environments; otherwise, check them in: # .python-version # pipenv # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. # However, in case of collaboration, if having platform-specific dependencies or dependencies # having no cross-platform support, pipenv may install dependencies that don't work, or not # install all needed dependencies. # Pipfile.lock # UV # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # uv.lock # poetry # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. # This is especially recommended for binary packages to ensure reproducibility, and is more # commonly ignored for libraries. # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control # poetry.lock # poetry.toml # pdm # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. # pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. # https://pdm-project.org/en/latest/usage/project/#working-with-version-control # pdm.lock # pdm.toml .pdm-python .pdm-build/ # pixi # Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. # pixi.lock # Pixi creates a virtual environment in the .pixi directory, just like venv module creates one # in the .venv directory. It is recommended not to include this directory in version control. .pixi # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm __pypackages__/ # Celery stuff celerybeat-schedule celerybeat.pid # Redis *.rdb *.aof *.pid # RabbitMQ mnesia/ rabbitmq/ rabbitmq-data/ # ActiveMQ activemq-data/ # SageMath parsed files *.sage.py # Environments .env .envrc .venv env/ venv/ ENV/ env.bak/ venv.bak/ # Spyder project settings .spyderproject .spyproject # Rope project settings .ropeproject # mkdocs documentation /site # mypy .mypy_cache/ .dmypy.json dmypy.json # Pyre type checker .pyre/ # pytype static type analyzer .pytype/ # Cython debug symbols cython_debug/ # PyCharm # JetBrains specific template is maintained in a separate JetBrains.gitignore that can # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore # and can be added to the global gitignore or merged into this file. For a more nuclear # option (not recommended) you can uncomment the following to ignore the entire idea folder. # .idea/ # Abstra # Abstra is an AI-powered process automation framework. # Ignore directories containing user credentials, local state, and settings. # Learn more at https://abstra.io/docs .abstra/ # Visual Studio Code # Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore # that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore # and can be added to the global gitignore or merged into this file. However, if you prefer, # you could uncomment the following to ignore the entire vscode folder # .vscode/ # Ruff stuff: .ruff_cache/ # PyPI configuration file .pypirc # Marimo marimo/_static/ marimo/_lsp/ __marimo__/ # Streamlit .streamlit/secrets.tomllocal_settings
本地:
local_settings.py编写本地特有配置线上:
local_settings.py编写线上特有配置(代码上传后需要自行创建)在
.gitignore中 Django 项目的local_settings.py会被忽略python# Django stuff: *.log local_settings.py db.sqlite3 db.sqlite3-journalpython# settings.py 编写共同的配置 try: from .local_settings import * except Exception: pass # local_settings.py 编写特有的配置(数据库、redis。。。)代码上传
powershellgit remote add origin git@github.com:用户名/仓库名.git # 添加仓库 输入密码 # 将密码写入 remote git remote remove origin # 删除原来的 remote git remote add origin https://用户名:密码@github.com/xxx/project.gitpowershellgit add. git commit -m "commit message" git push origin master
服务器
powershell# 安装 git yum install git mkdir -p /data/project # 新建目录 cd /data/project # 进入存放项目的位置 # 克隆代码(首次) git clone https://gitee.com/用户名/仓库名.git git clone git@gitee.com:用户名/仓库名.git # 需要SSH密钥 # 更新代码 git pull origin master代码版本(补充)
在本地的 git 每次执行
commit命令时,都会生成一个提交记录,如果执行git push也会将记录提交到代码仓库。各个版本之间进行切换:
powershellgit log # 查看目前的提交记录(当前的提交链) git reflog # 查看所有分支的提交记录(包括分支切换、提交、合并、重置等操作) git reset --hard xxx # 跳转至指定版本【注】此命令可以在本地、线上执行
密钥
为省去每次 push/pull 代码 都要输入密码,我们将通过密钥简化操作流程,同时为方便操作服务器通过 ssh 工具 + 秘钥 连接服务器 
在本地电脑上利用 SSH + 密钥 连接到远程服务器
ssh 工具
- mac,自带SSH、iTerm2
- win,git集成的ssh、xshell、SecureCRT、FinalShell本地电脑
powershell# ssh 工具(Git Bash) ssh-keygen -t rsa # 一直按回车直到出现密钥 cat ~/.ssh/id_rsa.pub # 读取公钥(检验是否已生成) ssh-copy-id -i ~/.ssh/id_rsa.pub 用户名@ID # 服务器:用户名@ID 输入密码登录服务器powershell# ssh 工具 >>> ssh 用户名@IP >>> 无需密码登录 # 使用如 FinalShell 等ssh工具,在配置好后不需要输入命令也能直接进行登录服务器
powershellssh-keygen -t rsa cat ~/.ssh/id_rsa.pub代码托管平台
powershell设置 -> SSH 公钥 settings -> SSH and GPG key 将本地电脑和服务器的公钥分别拷贝进去
配置服务器
MySQL
安装服务端 和 客户端
MariaDB 与 MySQL 同源,但是在云服务上 mysql 可能不好安装上
powershellyum install mariadb-server -y yum install mariadb -y # 安装 mariadb-server 时,mariadb 被作为依赖安装配置 数据库
powershellsystemctl start mariadb # 启动 systemctl stop mariadb # 停止 systemctl enable mariadb # 开机启动账号初始化
登录 root
powershellmysql -u root -proot 设置密码(可不设置)
sqlUPDATE USER SET password=password('123456') WHERE USER='root'; flush privileges;创建用户
若想要在外部访问服务器,则需要在服务器平台防火墙(安全组),添加
3306端口的外部访问权限sql-- 创建用户 CREATE USER 'stars'@'localhost' IDENTIFIED BY '123456'; -- 只能被本机访问 CREATE USER 'stars'@'100.100.%' IDENTIFIED BY '123456'; -- 可被外部访问 flush privileges; -- 更新授权表( INSERT / UPDATE / DELETE )创建数据库
sqlCREATE DATABASE 数据库名 DEFAULT CHARSET utf8 COLLATE utf8_general_ci; SHOW DATABASES; -- 列出当前用户有权限看到的所有数据库为用户授权
sqlGRANT ALL PRIVILEGES ON 数据库.* TO stars@'localhost'; GRANT SELECT, SHOW VIEW ON 数据库.* TO 'read'@'10.0.0.100'; --低权用户 GRANT CREATE ON *.* TO 'user'@'localhost'; -- 全局级,想建任何库都行 flush privileges;【补充】
sql-- 登录新创建的用户 mysql -u stars -p -- 默认登录的是 localhost,推荐重新创建一个 'stars'@'localhost' mysql -u stars -p -h 127.0.0.1 -- 强制使用 TCP/IP -- 更新密码 UPDATE user SET password=password('123456') WHERE user='stars'; flush privileges; -- 查看所有用户 select user,host,password from mysql.user; +-------+----------------+-------------------------------------------+ | user | host | password | +-------+----------------+-------------------------------------------+ | root | localhost | | | root | vm-0-11-centos | | | root | 127.0.0.1 | | | root | ::1 | | | | localhost | | | | vm-0-11-centos | | | stars | 127.0.0.1 | *6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9 | +-------+----------------+-------------------------------------------+ /* mysql 默认配置,在本机中可直接登录数据库无需账户和密码 若不想数据库可被任意账号登录,则删除匿名用户 */ -- 删除匿名用户 DROP USER ''@'localhost'; DROP USER ''@'vm-0-11-centos'; flush privileges;
Redis
安装
yum install redis -y配置
powershell# 打开配置文件 vim /etc/redis.conf # ?查找内容 -- 查找 ?requirepass # 'n' -- 下一个 # 'i' -- 编辑 requirepass 123456 # 修改密码 # 'Esc' -- 退出编辑状态 # ':wq' -- 保存并退出目前只能在本机连接,若要外部也能进行连接(MySQL 同理)
powershell配置文件 -> bind 0.0.0.0 云服务器 -> 安全组配置 -> 6378端口启动
powershellsystemctl start redis # 启动 systemctl restart redis # 重启 systemctl enable redis # 开机启动
Python3
安装 gcc,用于后续安装Python时编译源码
pythonyum install gcc -y yum groupinstall -y "Development Tools"安装Python3相关依赖
yum install zlib zlib-devel -y yum install bzip2 bzip2-devel -y yum install ncurses ncurses-devel -y yum install readline readline-devel -y yum install openssl openssl-devel -y yum install xz lzma xz-devel -y yum install sqlite sqlite-devel -y yum install gdbm gdbm-devel -y yum install tk tk-devel -y yum install mysql-devel -y yum install mariadb-devel -y yum install python39-devel -y yum install libffi-devel -y下载Python源码,https://www.python.org/ftp/python/
cd /data/ wget https://www.python.org/ftp/python/3.9.23/Python-3.9.23.tgz【注】如果没有wget,则先安装
yum install wget【注】直接在官网下载可能会很慢,此时可以选择切换成其它源
编译安装
解压
tar -xvf Python-3.9.23.tgz进入目录并编译安装
cd Python-3.9.23 ./configure make all make install测试
python3 --version /usr/local/bin/python3 /usr/local/bin/pip3 /usr/local/bin/pip3.9
虚拟环境
安装虚拟环境工具
virtualenv(Python官方社区的工具)pip3.9 install virtualenv创建虚拟环境目录 & 创建虚拟环境
cd /root mkdir /envs virtualenv /envs/<>env_name> --python=python3.9安装项目依赖的 pip包
powershellpip freeze > requirements.txt # 创建项目依赖文件 git clone https://gitee.com/用户名/仓库.git # 克隆代码powershellsource /envs/env_name/bin/activate # 激活虚拟环境 deactivate # 退出虚拟环境 rm -rf /envs/env_name # 删除虚拟环境 cd 仓库 pip install -r requirements.txt # 安装相关的第三方包
local_settings 设置线上配置
创建
local_settingspowershellcd /data/project/<project_name>/<project_name> vim local_settings.pylocal_settings.pypython# 指定收集静态文件的位置 import os from pathlib import Path BASE_DIR = Path(__file__).resolve().parent.parent STATIC_ROOT = os.path.join(BASE_DIR, "allstatic") DEBUG = False ALLOWED_HOSTS = ['*'] DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'db', # 数据库名字 'USER': 'stars', 'PASSWORD': '123456', # * 'HOST': '127.0.0.1', # ip 'PORT': 3306, } } CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", "CONNECTION_POOL_KWARGS": {"max_connections": 100}, "PASSWORD": "123456", # * } } } # Esc -> :wq收集静态文件
python manage.py collectstatic --noinput【注】若静态文件有变动,则需要重新收集
Nginx + uWSGI
为实现 高性能、高并发、稳定部署 的Web服务架构,使用 Nginx + uWSGI
- Nginx 作为反向代理服务器,负责处理静态资源、负载均衡和SSL终端,提升响应速度和安全性;
- uWSGI 作为应用服务器,负责运行Python Web应用(如Django、Flask),与Nginx配合实现动态请求的高效处理。

# 激活虚拟环境并安装 uwsgi + nginx
source /envs/<env_name>/bin/activate
yum install uwsgi -y
yum install nginx -yshell 脚本
# 先本地操作,之后再上传代码,便于代码管理
/data/project/<project_name>/shell
└── reboot.sh
└── stop.sh
└── django_project<project_name>_uwsgi.iniuWSGI
命令参数
# 测试是否可运行,若没问题 ctrl-c 关闭 uwsgi --http :80 --chdir /data/project/<project_name> --wsgi-file <project_name>/wsgi.py --master --processes 4 --static-map /static=/data/project/<project_name>/allstatic文件参数
ini# django_project<project_name>_uwsgi.ini [uwsgi] socket = 127.0.0.1:8001 # Nginx + uWSGI 在同一台服务器 chdir = /data/project/<project_name> home = /envs/<env_name>/ module = <project_name>.wsgi:application master = true processes = 4 chmod-socket = 666 vacuum = true enable-threads = true static-map = /static=/data/project/<project_name>/allstaticreboot.shbash#!/usr/bin/env bash echo -e "\033[34m--------------------wsgi process--------------------\033[0m" ps -ef|grep <project_name>_uwsgi.ini | grep -v grep sleep 0.5 echo -e '\n--------------------going to close--------------------' ps -ef |grep <project_name>_uwsgi.ini | grep -v grep | awk '{print $2}' | xargs kill -9 sleep 0.5 echo -e '\n----------check if the kill action is correct----------' /envs/<env_name>/bin/uwsgi --ini <project_name>_uwsgi.ini & >/dev/null echo -e '\n\033[42;1m----------------------started...----------------------\033[0m' sleep 1 ps -ef |grep <project_name>_uwsgi.ini | grep -v grepstop.shbash#!/usr/bin/env bash echo -e "\033[34m--------------------wsgi process--------------------\033[0m" ps -ef |grep <project_name>_uwsgi.ini | grep -v grep sleep 0.5 echo -e '\n--------------------going to close--------------------' ps -ef |grep <project_name>_uwsgi.ini | grep -v grep | awk '{print $2}' | xargs kill -9 sleep 0.5代码管理
bash# 本地 git add. git commit -m "commit message" git push origin master # 服务器 cd /data/project/<project_name> git pull origin master添加权限(在服务器中执行)
powershellchmod 755 reboot.sh stop.sh source /envs/<env_name>/bin/activate ./reboot.sh
nginx
cd /etc/nginx/
mv nginx.conf nginx.conf.bak # 备份原本的配置文件
vim nginx.confuser nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;
# Load dynamic modules. See /usr/share/doc/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
access_log /var/log/nginx/access.log main;
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
types_hash_max_size 4096;
include /etc/nginx/mime.types;
default_type application/octet-stream;
# Load modular configuration files from the /etc/nginx/conf.d directory.
# See http://nginx.org/en/docs/ngx_core_module.html#include
# for more information.
# include /etc/nginx/conf.d/*.conf;
# 反向代理本机端口
upstream django {
server 127.0.0.1:8001; # *
}
server {
listen 80;
listen [::]:80;
server_name _; # 域名(server_name.com;)
# Load configuration files for the default server block.
# include /etc/nginx/default.d/*.conf;
# 静态文件
location /static {
alias /data/project/<project_name>/allstatic/; # *
}
# 动态请求
location / {
uwsgi_pass django;
include uwsgi_params;
}
}
}systemctl start nginx # 启动
systemctl enable nginx # 开机启动域名和解析
- 申请域名 -> 备案
- 解析:将域名与服务器 ip 绑定
https
SSL证书 -> 免费证书 -> 创建证书
填写申请:绑定域名、验证 。。。
添加 DNS解析记录
下载证书文件 - Nginx
bash域名_nginx └── 域名_bundle.pem # 证书文件 └── 域名.key # 私钥文件将证书上传到服务器
/data/ssl修改 nginx 配置(增加对https的支持)
powershellcd /etc/nginx/ mv nginx.conf nginx.conf.http.bak # 备份原本的配置文件 vim nginx.confroboconfuser nginx; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; # Load dynamic modules. See /usr/share/doc/nginx/README.dynamic. include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 4096; include /etc/nginx/mime.types; default_type application/octet-stream; # Load modular configuration files from the /etc/nginx/conf.d directory. # See http://nginx.org/en/docs/ngx_core_module.html#include # for more information. # include /etc/nginx/conf.d/*.conf; # 反向代理本机端口 upstream django { server 127.0.0.1:8001; } server { listen 80; listen [::]:80; server_name <域名>; # 域名 rewrite ^(.*) https://$server_name$1 redirect; } server { listen 443 ssl; server_name <域名>; # 域名 ssl_certificate /data/ssl/<域名>_bundle.pem; # 证书文件 ssl_certificate_key /data/ssl/<域名>.key; # 私钥文件 ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_prefer_server_ciphers on; # 静态文件 location /static { alias /data/project/<project_name>/allstatic/; } # 动态请求 location / { uwsgi_pass django; include uwsgi_params; } } }重启
powershellsystemctl restart redis systemctl restart nginx cd /data/project/django-lufei/shell ./reboot.sh
数据库创建数据表
# 生成迁移文件(如果模型有变化)
python manage.py makemigrations
# 执行迁移,创建数据表
python manage.py migrate
# 创建测试数据
python scripts/init_admin.py
python scripts/init_customer.py遇到的问题
安装
mysqlclient包时出现的问题已安装了相关的依赖但是仍无法安装成功
解决办法:使用
PyMySQL作为替代pip install PyMySQLpowershell# init.py (与 settings.py 同级) import pymysql pymysql.install_as_MySQLdb()运行
uWSGI出现报错:ImportError: urllib3 v2 only supports OpenSSL 1.1.1+, currently the 'ssl' module is compiled with 'OpenSSL 1.0.2k-fips'urllib3版本太高,不兼容系统的OpenSSL 1.0.2解决办法:降级
urllib3powershellsource /envs/Django-LuFei<env_name>/bin/activate pip install "urllib3<2" pkill -9 uwsgi uwsgi --ini django_project<project_name>_uwsgi.ini &django.db.utils.NotSupportedError: MariaDB 10.4 or later is required (found 5.5.68).Django 4.x(以及你当前虚拟环境里装的版本)只支持 MariaDB ≥10.4,而系统里现在是 5.5.68,太老了,直接拒绝连接
解决办法:升级 MariaDB 到 10.4+
python# 0. 备份旧数据(可选) mysqldump --all-databases > /root/all.sql # 1. 卸掉旧版 sudo systemctl stop mysqld sudo yum remove -y mariadb* # 2. 写新 repo sudo tee /etc/yum.repos.d/mariadb.repo <<'EOF' [mariadb] name = MariaDB baseurl = https://mirrors.aliyun.com/mariadb/yum/10.6/centos7-amd64 gpgkey = https://mirrors.aliyun.com/mariadb/yum/RPM-GPG-KEY-MariaDB gpgcheck = 1 EOF # 3. 安装并启服务 sudo yum install -y MariaDB-server MariaDB-client sudo systemctl enable --now mariadb # 4. 安全初始化(设 root 密码等) sudo mysql_secure_installation # 5. 验证版本 mysql -uroot -p -e "SELECT VERSION();" # 应显示 10.6.x # 6. 回到项目 pip install mysqlclient # 或 pymysql 并加 django-pymysql 驱动 python manage.py makemigrations